Skip to content

Conversation

@cy-yun
Copy link

@cy-yun cy-yun commented Dec 3, 2025

A delegator that wraps a logger or its duck-type instance to add dynamic filtering based on GOOGLE_SDK_RUBY_LOGGING_GEMS environment variable, and error supression based on suppress_logger_errors parameter. To be used for client library & GAPIC layer logging.

@cy-yun
Copy link
Author

cy-yun commented Dec 3, 2025

These two pull requests introduce a standardized and dynamically configurable debug logging framework for Google Cloud Ruby clients and implement it in the Pub/Sub library.

The primary goal was to enable on-demand debug logging that could be controlled by the developer via configuring a custom logger and controlling an environment variable to turn logs on/off. This required passing a logger from the handwritten client (google-cloud-pubsub) down to the underlying GAPIC client (google-cloud-pubsub-v1). I needed to build a wrapper around the logger (creating a logger duck-type) that will evaluate the environment variable before emitting logs.

The Solution:

  • https://github.com/googleapis/ruby-core-libraries/pull/49/files creates the core component, GoogleSdkLoggerDelegator in google-logging-util gem. This class acts as a "duck-type" for a standard Ruby logger. Its key function is to wrap a real logger and check the GOOGLE_SDK_RUBY_LOGGING_GEMS environment variable before passing a log message through. If the variable isn't set for the specific gem, the delegator simply does nothing. This provides a reusable, centralized way to control logging across all Google Cloud Ruby libraries.

  • https://github.com/googleapis/google-cloud-ruby/pull/32136/files implements this framework in the Pub/Sub library. It modifies the handwritten google-cloud-pubsub client to accept a logger, wraps it with the GoogleSdkLoggerDelegator, and emits debug logs requested by the Pub/Sub team. It also includes an .owlbot.rb file which will trigger a post-processing code change in the GAPIC-layer which will allow the GAPIC clients to accept both Logger type and GoogleSdkLoggerDelegator type. The github workflows are failing because I set the version of google-logging-util gem to currently-non-existent, future version, 0.3.0 . The current latest version of google-logging-util gem is 0.2.0 . The version with the release of GoogleSdkLoggerDelegator would be 0.3.0 (correct me if I'm wrong).

Once the first 2 PRs are approved, I will create a new PR that passes the GoogleSdkLoggerDelegator down to the GAPIC layer (google-cloud-pubsub-v1).

Copy link
Member

@viacheslav-rostovtsev viacheslav-rostovtsev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this approach is fine. A reasonable alternative would be to require a wrapped logger to handle its errors (like Logger does) and trust that whatever errors are going to be raised from the underlying logger are the errors that the end-user wants to see.

# A delegator that wraps a logger or its duck-type instance to add dynamic
# filtering based on GOOGLE_SDK_RUBY_LOGGING_GEMS environment variable, and
# error supression based on suppress_logger_errors parameter
class GoogleSdkLoggerDelegator < SimpleDelegator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider calling it "Proxy" or "Wrapper" instead of "Delegator".

class GoogleSdkLoggerDelegator < SimpleDelegator
# @private
# The environment variable that controls which gems have logging enabled.
ENV_VAR = "GOOGLE_SDK_RUBY_LOGGING_GEMS".freeze

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"LOGGING_GEMS_ENV_VAR"

suppress_errors { super }
end

def debug message = nil, &block

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since it's Ruby 3.1 we can probably drop the block variable name and just use &


# Return the cached result if the ENV var hasn't changed.
if @cached_env_var_value == current_env_var
return @cached_logging_enabled_result

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cached instance-level variables should be explicitly initialized in the constructor.

def is_logger_type? logger
return false if logger.nil?
REQUIRED_LOGGER_METHODS.all? { |m| logger.respond_to? m }
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a class-level (static) method


def << msg
return true unless logging_enabled?
suppress_errors { super }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like handle_logging_errors would be a better name, similar to handle_writing_errors in Logger's LogDevice

# @param gem_name [String] The name of the gem this logger is for.
# @param logger [Logger] The custom logger instance to wrap.
# @param suppress_logger_errors [Boolean] Whether to swallow exceptions in the wrapped logger.
def initialize gem_name, logger, suppress_logger_errors: true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should make it same as Logger by giving it a list of Errors to reraise instead of a blanket variable.

require "google/logging/message"
require "google/logging/source_location"
require "google/logging/structured_formatter"
require "google/logging/google_sdk_filtered_logger"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants